import Vue from 'vue'
import debounce from 'throttle-debounce/debounce'
import merge from '../../src/utils/merge'
import { hasClass, addClass, removeClass } from '../../src/utils/dom'
import { orderBy, getColumnById, getRowIdentity } from './util'

const sortData = (data, states) => {
  const sortingColumn = states.sortingColumn
  if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
    return data
  }
  return orderBy(data, states.sortProp, states.sortOrder, sortingColumn.sortMethod, sortingColumn.sortBy)
}

const getKeysMap = function (array, rowKey) {
  const arrayMap = {};
  (array || []).forEach((row, index) => {
    arrayMap[getRowIdentity(row, rowKey)] = { row, index }
  })
  return arrayMap
}

const toggleRowSelection = function (states, row, selected) {
  let changed = false
  const selection = states.selection
  const index = selection.indexOf(row)
  if (typeof selected === 'undefined') {
    if (index === -1) {
      selection.push(row)
      changed = true
    } else {
      selection.splice(index, 1)
      changed = true
    }
  } else {
    if (selected && index === -1) {
      selection.push(row)
      changed = true
    } else if (!selected && index > -1) {
      selection.splice(index, 1)
      changed = true
    }
  }

  return changed
}

const toggleRowExpansion = function (states, row, expanded) {
  let changed = false
  const expandRows = states.expandRows
  if (typeof expanded !== 'undefined') {
    const index = expandRows.indexOf(row)
    if (expanded) {
      if (index === -1) {
        expandRows.push(row)
        changed = true
      }
    } else {
      if (index !== -1) {
        expandRows.splice(index, 1)
        changed = true
      }
    }
  } else {
    const index = expandRows.indexOf(row)
    if (index === -1) {
      expandRows.push(row)
      changed = true
    } else {
      expandRows.splice(index, 1)
      changed = true
    }
  }

  return changed
}

const TableStore = function (table, initialState = {}) {
  if (!table) {
    throw new Error('Table is required.')
  }
  this.table = table

  this.states = {
    rowKey: null,
    _columns: [],
    _oldColumns: [],
    originColumns: [],
    columns: [],
    fixedColumns: [],
    rightFixedColumns: [],
    leafColumns: [],
    fixedLeafColumns: [],
    rightFixedLeafColumns: [],
    leafColumnsLength: 0,
    fixedLeafColumnsLength: 0,
    rightFixedLeafColumnsLength: 0,
    isComplex: false,
    filteredData: null,
    data: null,
    sortingColumn: null,
    sortProp: null,
    sortOrder: null,
    isAllSelected: false,
    selection: [],
    reserveSelection: false,
    selectable: null,
    currentRow: null,
    hoverRow: null,
    filters: {},
    expandRows: [],
    defaultExpandAll: false,
    selectOnIndeterminate: false
  }

  for (let prop in initialState) {
    if (initialState.hasOwnProperty(prop) && this.states.hasOwnProperty(prop)) {
      this.states[prop] = initialState[prop]
    }
  }
}

TableStore.prototype.mutations = {
  setData (states, data) {
    const dataInstanceChanged = states._data !== data
    states._data = data

    Object.keys(states.filters).forEach((columnId) => {
      const values = states.filters[columnId]
      if (!values || values.length === 0) return
      const column = getColumnById(this.states, columnId)
      if (column && column.filterMethod) {
        data = data.filter((row) => {
          return values.some(value => column.filterMethod.call(null, value, row, column))
        })
      }
    })

    states.filteredData = data
    states.data = sortData((data || []), states)

    this.updateCurrentRow()

    const rowKey = states.rowKey

    if (!states.reserveSelection) {
      if (dataInstanceChanged) {
        this.clearSelection()
      } else {
        this.cleanSelection()
      }
      this.updateAllSelected()
    } else {
      if (rowKey) {
        const selection = states.selection
        const selectedMap = getKeysMap(selection, rowKey)

        states.data.forEach((row) => {
          const rowId = getRowIdentity(row, rowKey)
          const rowInfo = selectedMap[rowId]
          if (rowInfo) {
            selection[rowInfo.index] = row
          }
        })

        this.updateAllSelected()
      } else {
        console.warn('WARN: rowKey is required when reserve-selection is enabled.')
      }
    }

    const defaultExpandAll = states.defaultExpandAll
    if (defaultExpandAll) {
      this.states.expandRows = (states.data || []).slice(0)
    } else if (rowKey) {
      // update expandRows to new rows according to rowKey
      const ids = getKeysMap(this.states.expandRows, rowKey)
      let expandRows = []
      for (const row of states.data) {
        const rowId = getRowIdentity(row, rowKey)
        if (ids[rowId]) {
          expandRows.push(row)
        }
      }
      this.states.expandRows = expandRows
    } else {
      // clear the old rows
      this.states.expandRows = []
    }

    Vue.nextTick(() => this.table.updateScrollY())
  },

  changeSortCondition (states, options) {
    states.data = sortData((states.filteredData || states._data || []), states)

    const { $el, highlightCurrentRow } = this.table
    if ($el && highlightCurrentRow) {
      const data = states.data
      const tr = $el.querySelector('tbody').children
      const rows = [].filter.call(tr, row => hasClass(row, 'el-table__row'))
      const row = rows[data.indexOf(states.currentRow)];

      [].forEach.call(rows, row => removeClass(row, 'current-row'))
      addClass(row, 'current-row')
    }

    if (!options || !options.silent) {
      this.table.$emit('sort-change', {
        column: this.states.sortingColumn,
        prop: this.states.sortProp,
        order: this.states.sortOrder
      })
    }

    Vue.nextTick(() => this.table.updateScrollY())
  },

  sort (states, options) {
    const { prop, order } = options
    if (prop) {
      states.sortProp = prop
      states.sortOrder = order || 'ascending'
      Vue.nextTick(() => {
        for (let i = 0, length = states.columns.length; i < length; i++) {
          let column = states.columns[i]
          if (column.property === states.sortProp) {
            column.order = states.sortOrder
            states.sortingColumn = column
            break
          }
        }

        if (states.sortingColumn) {
          this.commit('changeSortCondition')
        }
      })
    }
  },

  filterChange (states, options) {
    let { column, values, silent } = options
    if (values && !Array.isArray(values)) {
      values = [values]
    }

    const prop = column.property
    const filters = {}

    if (prop) {
      states.filters[column.id] = values
      filters[column.columnKey || column.id] = values
    }

    let data = states._data

    Object.keys(states.filters).forEach((columnId) => {
      const values = states.filters[columnId]
      if (!values || values.length === 0) return
      const column = getColumnById(this.states, columnId)
      if (column && column.filterMethod) {
        data = data.filter((row) => {
          return values.some(value => column.filterMethod.call(null, value, row, column))
        })
      }
    })

    states.filteredData = data
    states.data = sortData(data, states)

    if (!silent) {
      this.table.$emit('filter-change', filters)
    }

    Vue.nextTick(() => this.table.updateScrollY())
  },

  insertColumn (states, column, index, parent) {
    let array = states._columns
    if (parent) {
      array = parent.children
      if (!array) array = parent.children = []
    }
    if (typeof index !== 'undefined') {
      array.splice(index, 0, column)
    } else {
      array.push(column)
    }
    states._oldColumns = [].concat(array);
    if (column.type === 'selection') {
      states.selectable = column.selectable
      states.reserveSelection = column.reserveSelection
    }

    if (this.table.$ready) {
      this.updateColumns() // hack for dynamics insert column
      this.scheduleLayout()
    }
  },

  removeColumn (states, column, parent) {
    let array = states._columns
    if (parent) {
      array = parent.children
      if (!array) array = parent.children = []
    }
    if (array) {
      array.splice(array.indexOf(column), 1)
    }

    if (this.table.$ready) {
      this.updateColumns() // hack for dynamics remove column
      this.scheduleLayout()
    }
  },

  sortColumn (states, sortColumn) {
    if(!sortColumn) return
    let initTmp = [];
    let sortTmp = [];
    let fixedTmp = [];
    if(sortColumn.length < 1){
      states._columns.map((val)=>{
        if (val.label && !val.fixed) {
          initTmp.push({
            key: val.property,
            label: val.label,
            isActive: true
          })
        }
      })
      this.table.changeColumn(initTmp)
    }else {
      states._oldColumns.map((val)=>{
        if((val.type !== 'default' && !val.label) || val.fixed){
          fixedTmp.push(val)
        }
      })
      sortTmp = sortTmp.concat(fixedTmp)
      sortColumn.map((item)=>{
        states._oldColumns.map((val)=>{
          if(val.label && !val.fixed && item.label == val.label && item.isActive){
            sortTmp.push(val)
          }
        })
      })
      states._columns = sortTmp
    }

    if (this.table.$ready) {
      this.updateColumns() // hack for dynamics remove column
      this.scheduleLayout()
    }
  },

  setHoverRow (states, row) {
    states.hoverRow = row
  },

  setCurrentRow (states, row) {
    const oldCurrentRow = states.currentRow
    states.currentRow = row

    if (oldCurrentRow !== row) {
      this.table.$emit('current-change', row, oldCurrentRow)
    }
  },

  rowSelectedChanged (states, row) {
    const changed = toggleRowSelection(states, row)
    const selection = states.selection

    if (changed) {
      const table = this.table
      table.$emit('selection-change', selection ? selection.slice() : [])
      table.$emit('select', selection, row)
    }

    this.updateAllSelected()
  },

  toggleAllSelection: debounce(10, function (states) {
    const data = states.data || []
    if (data.length === 0) return
    const selection = this.states.selection
    // when only some rows are selected (but not all), select or deselect all of them
    // depending on the value of selectOnIndeterminate
    const value = states.selectOnIndeterminate
      ? !states.isAllSelected
      : !(states.isAllSelected || selection.length)
    let selectionChanged = false

    data.forEach((item, index) => {
      if (states.selectable) {
        if (states.selectable.call(null, item, index) && toggleRowSelection(states, item, value)) {
          selectionChanged = true
        }
      } else {
        if (toggleRowSelection(states, item, value)) {
          selectionChanged = true
        }
      }
    })

    const table = this.table
    if (selectionChanged) {
      table.$emit('selection-change', selection ? selection.slice() : [])
    }
    table.$emit('select-all', selection)
    states.isAllSelected = value
  })
}

const doFlattenColumns = (columns) => {
  const result = []
  columns.forEach((column) => {
    if (column.children) {
      result.push.apply(result, doFlattenColumns(column.children))
    } else {
      result.push(column)
    }
  })
  return result
}

TableStore.prototype.updateColumns = function () {
  const states = this.states
  const _columns = states._columns || []
  states.fixedColumns = _columns.filter((column) => column.fixed === true || column.fixed === 'left')
  states.rightFixedColumns = _columns.filter((column) => column.fixed === 'right')

  if (states.fixedColumns.length > 0 && _columns[0] && _columns[0].type === 'selection' && !_columns[0].fixed) {
    _columns[0].fixed = true
    states.fixedColumns.unshift(_columns[0])
  }

  const notFixedColumns = _columns.filter(column => !column.fixed)
  states.originColumns = [].concat(states.fixedColumns).concat(notFixedColumns).concat(states.rightFixedColumns)

  const leafColumns = doFlattenColumns(notFixedColumns)
  const fixedLeafColumns = doFlattenColumns(states.fixedColumns)
  const rightFixedLeafColumns = doFlattenColumns(states.rightFixedColumns)

  states.leafColumnsLength = leafColumns.length
  states.fixedLeafColumnsLength = fixedLeafColumns.length
  states.rightFixedLeafColumnsLength = rightFixedLeafColumns.length

  states.columns = [].concat(fixedLeafColumns).concat(leafColumns).concat(rightFixedLeafColumns)
  states.isComplex = states.fixedColumns.length > 0 || states.rightFixedColumns.length > 0
}

TableStore.prototype.isSelected = function (row) {
  return (this.states.selection || []).indexOf(row) > -1
}

TableStore.prototype.clearSelection = function () {
  const states = this.states
  states.isAllSelected = false
  const oldSelection = states.selection
  if (states.selection.length) {
    states.selection = []
  }
  if (oldSelection.length > 0) {
    this.table.$emit('selection-change', states.selection ? states.selection.slice() : [])
  }
}

TableStore.prototype.setExpandRowKeys = function (rowKeys) {
  const expandRows = []
  const data = this.states.data
  const rowKey = this.states.rowKey
  if (!rowKey) throw new Error('[Table] prop row-key should not be empty.')
  const keysMap = getKeysMap(data, rowKey)
  rowKeys.forEach((key) => {
    const info = keysMap[key]
    if (info) {
      expandRows.push(info.row)
    }
  })

  this.states.expandRows = expandRows
}

TableStore.prototype.toggleRowSelection = function (row, selected) {
  const changed = toggleRowSelection(this.states, row, selected)
  if (changed) {
    this.table.$emit('selection-change', this.states.selection ? this.states.selection.slice() : [])
  }
}

TableStore.prototype.toggleRowExpansion = function (row, expanded) {
  const changed = toggleRowExpansion(this.states, row, expanded)
  if (changed) {
    this.table.$emit('expand-change', row, this.states.expandRows)
    this.scheduleLayout()
  }
}

TableStore.prototype.isRowExpanded = function (row) {
  const { expandRows = [], rowKey } = this.states
  if (rowKey) {
    const expandMap = getKeysMap(expandRows, rowKey)
    return !!expandMap[getRowIdentity(row, rowKey)]
  }
  return expandRows.indexOf(row) !== -1
}

TableStore.prototype.cleanSelection = function () {
  const selection = this.states.selection || []
  const data = this.states.data
  const rowKey = this.states.rowKey
  let deleted
  if (rowKey) {
    deleted = []
    const selectedMap = getKeysMap(selection, rowKey)
    const dataMap = getKeysMap(data, rowKey)
    for (let key in selectedMap) {
      if (selectedMap.hasOwnProperty(key) && !dataMap[key]) {
        deleted.push(selectedMap[key].row)
      }
    }
  } else {
    deleted = selection.filter((item) => {
      return data.indexOf(item) === -1
    })
  }

  deleted.forEach((deletedItem) => {
    selection.splice(selection.indexOf(deletedItem), 1)
  })

  if (deleted.length) {
    this.table.$emit('selection-change', selection ? selection.slice() : [])
  }
}

TableStore.prototype.clearFilter = function () {
  const states = this.states
  const { tableHeader, fixedTableHeader, rightFixedTableHeader } = this.table.$refs
  let panels = {}

  if (tableHeader) panels = merge(panels, tableHeader.filterPanels)
  if (fixedTableHeader) panels = merge(panels, fixedTableHeader.filterPanels)
  if (rightFixedTableHeader) panels = merge(panels, rightFixedTableHeader.filterPanels)

  const keys = Object.keys(panels)
  if (!keys.length) return

  keys.forEach(key => {
    panels[key].filteredValue = []
  })

  states.filters = {}

  this.commit('filterChange', {
    column: {},
    values: [],
    silent: true
  })
}

TableStore.prototype.clearSort = function () {
  const states = this.states
  if (!states.sortingColumn) return
  states.sortingColumn.order = null
  states.sortProp = null
  states.sortOrder = null

  this.commit('changeSortCondition', {
    silent: true
  })
}

TableStore.prototype.updateAllSelected = function () {
  const states = this.states
  const { selection, rowKey, selectable, data } = states
  if (!data || data.length === 0) {
    states.isAllSelected = false
    return
  }

  let selectedMap
  if (rowKey) {
    selectedMap = getKeysMap(states.selection, rowKey)
  }

  const isSelected = function (row) {
    if (selectedMap) {
      return !!selectedMap[getRowIdentity(row, rowKey)]
    } else {
      return selection.indexOf(row) !== -1
    }
  }

  let isAllSelected = true
  let selectedCount = 0
  for (let i = 0, j = data.length; i < j; i++) {
    const item = data[i]
    const isRowSelectable = selectable && selectable.call(null, item, i)
    if (!isSelected(item)) {
      if (!selectable || isRowSelectable) {
        isAllSelected = false
        break
      }
    } else {
      selectedCount++
    }
  }

  if (selectedCount === 0) isAllSelected = false

  states.isAllSelected = isAllSelected
}

TableStore.prototype.scheduleLayout = function (updateColumns) {
  if (updateColumns) {
    this.updateColumns()
  }
  this.table.debouncedUpdateLayout()
}

TableStore.prototype.setCurrentRowKey = function (key) {
  const states = this.states
  const rowKey = states.rowKey
  if (!rowKey) throw new Error('[Table] row-key should not be empty.')
  const data = states.data || []
  const keysMap = getKeysMap(data, rowKey)
  const info = keysMap[key]
  states.currentRow = info ? info.row : null
}

TableStore.prototype.updateCurrentRow = function () {
  const states = this.states
  const table = this.table
  const data = states.data || []
  const oldCurrentRow = states.currentRow

  if (data.indexOf(oldCurrentRow) === -1) {
    states.currentRow = null

    if (states.currentRow !== oldCurrentRow) {
      table.$emit('current-change', null, oldCurrentRow)
    }
  }
}

TableStore.prototype.commit = function (name, ...args) {
  const mutations = this.mutations
  if (mutations[name]) {
    mutations[name].apply(this, [this.states].concat(args))
  } else {
    throw new Error(`Action not found: ${name}`)
  }
}

export default TableStore
